package utils;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

public class CollectUtils {


    /**
     * 判断指定集合是否是空的
     * @param collection
     * @return
     */
    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.isEmpty();
    }

    /**
     * 判断指定集合是否不是空的
     * @param collection
     * @return
     */
    public static boolean isNotEmpty(Collection<?> collection) {
        return !isEmpty(collection);
    }


    /**
     * 将集合转为Map形式
     * @param target 目标集合
     * @param key map中的key
     * @param value map中的value
     * @param <E>
     * @param <K>
     * @param <V>
     * @return
     */
    public static <E,K,V> Map<K,V> toHashMap(Collection<E> target, Function<E,K> key, Function<E,V> value){
        return toMap(target,key,value,HashMap::new);
    }

    /**
     * 将集合转为Map形式，map中的value是列表中的对象
     * @param target 目标集合
     * @param key map中的key
     * @param <E>
     * @param <K>
     * @return
     */
    public static <E,K> Map<K,E> toHashMap(Collection<E> target, Function<E,K> key){
        return toMap(target,key,Function.identity(),HashMap::new);
    }

    /**
     * 将集合转为Map形式
     * @param target 目标集合
     * @param key map中的key
     * @param value map中的value
     * @param <E>
     * @param <K>
     * @param <V>
     * @return
     */
    public static <E,K,V> Map<K,V> toLinkedMap(Collection<E> target, Function<E,K> key, Function<E,V> value){
        return toMap(target,key,value,LinkedHashMap::new);
    }

    /**
     * 将集合转为Map形式
     * @param target 目标集合
     * @param key map中的key
     * @param value map中的value
     * @param map 返回的Map类型，可以是HashMap,LinkedMap等
     * @param <E>
     * @param <K>
     * @param <V>
     * @return
     */
    public static <E,K,V> Map<K,V> toMap(Collection<E> target, Function<E,K> key, Function<E,V> value,
                                         Supplier<Map<K, V>> map){
        if(isNotEmpty(target)){
            return target.stream().collect(Collectors.toMap(e -> key.apply(e),e -> value.apply(e),(e1,e2) -> e1,
                    map==null ? HashMap::new : map));
        }
        return map.get();
    }


    /**
     * 从目标集合中取出val值的集合并返回
     * @param target 目标集合
     * @param val
     * @param <E>
     * @param <V>
     * @return
     */
    public static <E,V> List<V> getList(Collection<E> target, Function<E,V> val){
        return getCollection(target,val,ArrayList::new);
    }

    /**
     * 从目标集合中取出val值的集合并返回
     * @param target 目标集合
     * @param val 是一个集合
     * @param <E>
     * @param <V>
     * @return
     */
    public static <T,E,V extends Collection<T>> List<T> getFlatList(Collection<E> target, Function<E,V> val){
        return getFlatCollection(target,val,ArrayList::new);
    }

    /**
     * 从目标集合中取出val值的集合并返回
     * @param target 目标集合
     * @param val
     * @param <E>
     * @param <V>
     * @return
     */
    public static <E,V> Set<V> getSet(Collection<E> target, Function<E,V> val){
        return getCollection(target,val,HashSet::new);
    }

    /**
     * 从目标集合中取出val值的集合并返回
     * @param target 目标集合
     * @param val
     * @param <E>
     * @param <V>
     * @return
     */
    public static <T,E,V extends Collection<T>> Set<T> getFlatSet(Collection<E> target, Function<E,V> val){
        return getFlatCollection(target,val,HashSet::new);
    }

    /**
     * 从目标集合中取出val值的集合并返回
     * @param target 目标集合
     * @param val
     * @param collection 返回的集合类型，可以是ArrayList,HashSet,TreeSet等
     * @param <E>
     * @param <V>
     * @param <C>
     * @return
     */
    public static <E,V,C extends Collection<V>> C getCollection(Collection<E> target, Function<E,V> val,
                                                                Supplier<C> collection){
        collection = collection!=null ? collection : () -> (C) new ArrayList<V>();
        if(isEmpty(target)){
            return collection.get();
        }
        C vs = collection.get();
        if(vs instanceof TreeSet && ((TreeSet) vs).comparator() == null) {
            collection = () -> (C) new TreeSet<V>(Comparator.comparingInt(Object::hashCode));
        }
        return target.stream().filter(e -> null != val.apply(e))
                .map(e -> val.apply(e))
                .collect(Collectors.toCollection(collection));
    }

    /**
     * 从目标集合中取出val值的集合并返回
     * @param target 目标集合
     * @param val
     * @param collection 返回的集合类型，可以是ArrayList,HashSet,TreeSet等
     * @param <E>
     * @param <V>
     * @param <C>
     * @return
     */
    public static <T,E,V extends Collection<T>,C extends Collection<T>> C getFlatCollection(Collection<E> target,
                                                                                            Function<E,V> val, Supplier<C> collection){
        if(isEmpty(target)){
            return null;
        }
        collection = collection!=null ? collection : () -> (C) new ArrayList<V>();
        C vs = collection.get();
        if(vs instanceof TreeSet && ((TreeSet) vs).comparator() == null) {
            collection = () -> (C) new TreeSet<T>(Comparator.comparingInt(Object::hashCode));
        }
        return target.stream().filter(e -> null != val.apply(e))
                .flatMap(e -> val.apply(e).stream())
                .collect(Collectors.toCollection(collection));
    }

    /**
     * 对集合中某个属性相同的对象进行去重
     * @param target 目标列表
     * @param key 属性(该属性相同的对象会去重)
     * @param <T>
     * @return 去重后的对象集合
     */
    public static <T,C extends Collection<T>> C distinctByKey(C target, Function<T, ?> key) throws IllegalAccessException, InstantiationException {
        if(isNotEmpty(target)){
            C res = (C) target.getClass().newInstance();
            res.addAll(toLinkedMap(target,key,Function.identity()).values());
            return res;
        }
        return target;
    }

    /**
     * 把指定集合targe，根据key分组
     * @param target
     * @param key
     * @param <E>
     * @param <V>
     * @return
     */
    public static <E,V> HashMap<V, List<E>> groupByKey(Collection<E> target, Function<E,V> key){
        return groupByKey(target,key,HashMap::new);
    }

    /**
     * 把指定集合targe，根据key分组
     * @param target
     * @param key
     * @param map 自定义返回的Map类型，
     * @param <E>
     * @param <V>
     * @param <M>
     * @return
     */
    public static <E, V, M extends Map<V, List<E>>> M groupByKey(Collection<E> target,Function<E,V> key, Supplier<M> map){
        return groupByKey(target,key,map,toList());
    }

    /**
     * 把指定集合targe，根据key分组
     * @param target
     * @param key
     * @param map 自定义返回的Map类型，
     * @param collection 自定义返回的map中，value的类型，可以是List,Set等
     * @param <E>
     * @param <V>
     * @param <C>
     * @param <M>
     * @return
     */
    public static <E, V, C extends Collection<E>, M extends Map<V, C>> M groupByKey(Collection<E> target,
                                                                                    Function<E,V> key, Supplier<M> map, Supplier<C> collection){
        return groupByKey(target,key,map,Collectors.toCollection(collection));
    }

    /**
     * 把指定集合targe，根据key分组
     * @param target
     * @param key
     * @param map 自定义返回的Map类型
     * @param collector 自定义返回的map中，value的类型，可以是Collectors.toList(),Collectors.toSet()等
     * @param <E>
     * @param <V>
     * @param <C>
     * @param <M>
     * @return
     */
    public static <E, V, C extends Collection<E>, M extends Map<V, C>> M groupByKey(Collection<E> target,
                                                                                    Function<E,V> key, Supplier<M> map, Collector<E, ?, C> collector){
        if(isNotEmpty(target)){
            return target.stream().filter(e -> key.apply(e)!=null).collect(Collectors.groupingBy(key, map, collector));
        }
        return map.get();
    }


    /**
     * 对目标集合中某个属性进行求和
     * 只支持 int long double BigDecimal 这4中类型的求和
     * @param target
     * @param prop
     * @param cls 进行求和的字段类型
     * @param <E>
     * @param <V>
     * @return
     */
    public static <E, V> V sum(Collection<E> target, Function<E,V> prop, Class<V> cls){
        if(isNotEmpty(target)){
            if(cls == Integer.class){
                return (V) new Integer(target.stream().filter(t -> null != prop.apply(t))
                        .mapToInt(t -> (Integer) prop.apply(t)).sum());
            } else if(cls == Double.class) {
                return (V) new Double(target.stream().filter(t -> null != prop.apply(t))
                        .mapToDouble(t -> (Double) prop.apply(t)).sum());
            } else if(cls == Long.class) {
                return (V) new Long(target.stream().filter(t -> null != prop.apply(t))
                        .mapToLong(t -> (Long) prop.apply(t)).sum());
            } else if(cls == BigDecimal.class) {
                return (V) target.stream().filter(t -> null != prop.apply(t))
                        .map(t -> (BigDecimal) prop.apply(t))
                        .reduce(BigDecimal.ZERO,BigDecimal::add);
            } else {
                throw new IllegalArgumentException("不支持该字段类型的求和");
            }
        }
        if(cls == Integer.class){
            return (V) new Integer(0);
        } else if(cls == Double.class) {
            return (V) new Double(0);
        } else if(cls == Long.class) {
            return (V) new Long(0);
        } else if(cls == BigDecimal.class) {
            return (V) BigDecimal.ZERO;
        }else {
            throw new IllegalArgumentException("不支持该字段类型的求和");
        }
    }

    /**
     * 对目标集合中某个 int或Integer 属性进行求和
     * @param target
     * @param prop
     * @param <E>
     * @return
     */
    public static <E> int sumForInt(Collection<E> target, Function<E,Integer> prop){
        return sum(target,prop,Integer.class);
    }

    /**
     * 对目标集合中某个 double或Double 属性进行求和
     * @param target
     * @param prop
     * @param <E>
     * @return
     */
    public static <E> double sumForDouble(Collection<E> target, Function<E,Double> prop){
        return sum(target,prop,Double.class);
    }

    /**
     * 对目标集合中某个 long或Long 属性进行求和
     * @param target
     * @param prop
     * @param <E>
     * @return
     */
    public static <E> long sumForLong(Collection<E> target, Function<E,Long> prop){
        return sum(target,prop,Long.class);
    }

    /**
     * 对目标集合中某个 BigDecimal 属性进行求和
     * @param target
     * @param prop
     * @param <E>
     * @return
     */
    public static <E> BigDecimal sumForBigDecimal(Collection<E> target, Function<E,BigDecimal> prop){
        return sum(target,prop,BigDecimal.class);
    }

    /**
     * 获取目标集合中获取指定属性值最小的元素
     * @param target
     * @param prop
     * @param <E>
     * @param <V> 需要实现 Comparable 接口
     * @return
     */
    public static <E, V extends Comparable<V>> E min(Collection<E> target, Function<E,V> prop){
        if(isNotEmpty(target)){
            return target.stream().filter(t -> null != prop.apply(t))
                    .min(Comparator.comparing(t -> prop.apply(t))).orElse(null);
        }
        return null;
    }

    /**
     * 获取标集合中最小值
     * @param target
     * @param <E> 集合中的元素需要实现 Comparable 接口
     * @return
     */
    public static <E extends Comparable<E>> E min(Collection<E> target){
        if(isNotEmpty(target)){
            return target.stream().filter(e -> null != e)
                    .min(Comparator.naturalOrder()).orElse(null);
        }
        return null;
    }

    /**
     * 获取目标集合中获取指定属性值最大的元素
     * @param target
     * @param prop
     * @param <E>
     * @param <V> 需要实现 Comparable 接口
     * @return
     */
    public static <E, V extends Comparable<V>> E max(Collection<E> target, Function<E,V> prop){
        if(isNotEmpty(target)){
            return target.stream().filter(t -> null != prop.apply(t))
                    .max(Comparator.comparing(t -> prop.apply(t))).orElse(null);
        }
        return null;
    }

    /**
     * 获取标集合中最大值
     * @param target
     * @param <E> 集合中的元素需要实现 Comparable 接口
     * @return
     */
    public static <E extends Comparable<E>> E max(Collection<E> target){
        if(isNotEmpty(target)){
            return target.stream().filter(e -> null != e)
                    .max(Comparator.naturalOrder()).orElse(null);
        }
        return null;
    }

    /**
     * class Shop {
     *         private String id;
     *         private String name;
     *         private String telphone;
     *     }
     * class Product {
     *         private String productId;
     *         private String productName;
     *         private String shopId;
     *         private String shopName;
     *         private String shopTelphone;
     *     }
     * List<Product> list = new ArrayList<>();
     * 使用：setTargetProperty(list,"shopId",(ids)-> {
     *          List<Shop> shops = shopService.queryByIds(ids);
     *          if(shops!=null) return shops.stream().collect(toMap(Shop::getId,Function.identity()));
     *      },new String[]{"shopName","name"},new String[]{"shopTelphone","telphone"});
     * 解释:
     *      1.从list中取出shopId属性放到一个列表中(也就是ids，这里表示店铺id列表)
     *      2.根据ids查询店铺数据，并封装成Map形式，key：店铺id，value：店铺对象
     *      3.new String[]{"shopName","name"}：从Shop对象中取出name属性值，放到Product对象中的shopName字段中
     *      4.new String[]{"shopName"}：从Shop对象中取出shopName属性值，放到Product对象中的shopName字段中
     *
     * @param target 目标列表
     * @param idName 属性名称
     * @param datas 根据idName属性列表 查出来的数据(map形式，key是idName属性值，value是对象)
     * @param targetProerty 要设置值的目标属性
     * @param <T>
     * @param <K>
     * @param <V>
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    public static <T,K,V> void setTargetProperty(Collection<T> target, String idName,
                                                 Function<List<K>, Map<K,V>> datas, String[] ...targetProerty) throws NoSuchFieldException, IllegalAccessException {
        if (target!=null && target.size()>0) {
            Class<?> targetClass = target.iterator().next().getClass();
            Field idf = targetClass.getDeclaredField(idName);
            idf.setAccessible(true);
            List<K> ids = new ArrayList<K>();
            for (T t : target) {
                K k = (K) idf.get(t);
                if (null != k) {
                    ids.add(k);
                }
            }
            Map<K, V> valueMap = datas.apply(ids);
            if (valueMap==null || valueMap.size()<1) {
                return;
            }
            Class<?> valueClass = valueMap.values().iterator().next().getClass();
            Field[] targetNameArr = new Field[targetProerty.length];
            Field[] sourceNameArr = new Field[targetProerty.length];
            for(int i =0;i<targetProerty.length;i++){
                targetNameArr[i] = targetClass.getDeclaredField(targetProerty[i][0]);
                targetNameArr[i].setAccessible(true);
                if (targetProerty[i].length==1) {
                    sourceNameArr[i] = valueClass.getDeclaredField(targetProerty[i][0]);
                } else {
                    sourceNameArr[i] = valueClass.getDeclaredField(targetProerty[i][1]);
                }
                sourceNameArr[i].setAccessible(true);
            }
            for (T t : target) {
                for(int i = 0; i<targetNameArr.length; i++){
                    V v = valueMap.get(idf.get(t));
                    targetNameArr[i].set(t, sourceNameArr[i].get(v));
                }
            }
        }
    }

    /**
     * class Shop {
     *         private String id;
     *         private String name;
     *         private String telphone;
     *     }
     * class Product {
     *         private String productId;
     *         private String productName;
     *         private String shopId;
     *         private String shopName;
     *         private String shopTelphone;
     *     }
     * List<Product> list = new ArrayList<>();
     * 使用：setTargetProperty(list,"shopId",(List<String> ids) -> queryShopId2Name(ids), "shopName");
     * 解释:
     *      1.从list中取出shopId属性放到一个列表中(也就是ids，这里表示店铺id列表)
     *      2.根据ids查询店铺数据，并封装成Map形式，也就是queryShopId2Name方法需要返回一个map，其中 key：店铺id，value：这里是门店名称
     *      3."shopName"：从map中取出门店名称，放到Product对象中的shopName字段中
     *
     * @param target 目标列表
     * @param idName 属性名称
     * @param datas 根据idName属性列表 查出来的数据(map形式，key是idName属性值，targetProerty属性值)
     * @param targetProerty 要设置值的目标属性
     * @param <T>
     * @param <K>
     * @param <V>
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    public static <T,K,V> void setTargetProperty(Collection<T> target, String idName,
                                                 Function<List<K>, Map<K,V>> datas, String targetProerty) throws NoSuchFieldException, IllegalAccessException {
        if (target!=null && target.size()>0) {
            Class<?> targetClass = target.iterator().next().getClass();
            Field idf = targetClass.getDeclaredField(idName);
            idf.setAccessible(true);
            List<K> ids = new ArrayList<K>();
            for (T t : target) {
                K k = (K) idf.get(t);
                if (null != k) {
                    ids.add(k);
                }
            }
            Map<K, V> valueMap = datas.apply(ids);
            if (valueMap==null || valueMap.size()<1) {
                return;
            }
            Field targetName = targetClass.getDeclaredField(targetProerty);
            targetName.setAccessible(true);
            for (T t : target) {
                targetName.set(t, valueMap.get(idf.get(t)));
            }
        }
    }


    /**
     * 示例：
     * class Shop {
     *         private String id;
     *         private String name;
     *         private String telphone;
     *     }
     * class Product {
     *         private String productId;
     *         private String productName;
     *         private String shopId;
     *         private String shopName;
     *         private String shopTelphone;
     *     }
     * List<Product> list = new ArrayList<>();
     * 使用1：setTargetProperty(list,Product::getShopId,,(List<String> ids) -> queryShopId2Name(ids),
     *                      (product,val) -> product.setShopName(val));
     * 解释:
     *      1.从list中取出shopId属性放到一个列表中(也就是ids，这里表示店铺id列表)
     *      2.根据ids查询店铺数据，并封装成Map形式，key：店铺id，value：这里是门店名称
     *      3.(product,val) -> product.setShopName(val) 在这里是把val值设置到product的shopName属性中
     *
     * 使用2：setTargetProperty(list,Product::getShopId,
     *                      (List<String> ids) -> {
     *                          List<Shop> shops = shopService.queryByIds(ids);
     *                          if(shops!=null) return shops.stream().collect(toMap(Shop::getId,Function.identity()));
     *                      }
     *                      (product,shop) -> {
     *                          product.setShopName(shop.getName());
     *                          product.setShopTelphone(shop.getTelphone());
     *                      });
     * 解释:
     *      1.从list中取出shopId属性放到一个列表中(也就是ids，这里表示店铺id列表)
     *      2.根据ids查询店铺数据，并封装成Map形式，key：店铺id，value：这里是门店对象
     * @param data
     * @param source
     * @param datas
     * @param con
     * @param <T>
     * @param <K>
     * @param <M>
     * @param <V>
     */
    public static <T, K, M extends Map<K,V>, V> void setTargetProperty(List<T> data, Function<T, K> source,
                                                                       Function<List<K>, M> datas, BiConsumer<T, V> con) {
        if (isNotEmpty(data)) {
            List<K> ids = data.stream().filter(obj -> null != source.apply(obj)).map(obj -> source.apply(obj))
                    .collect(Collectors.toList());
            final M map = datas.apply(ids);
            data.stream().forEach(target -> con.accept(target,map.get(source.apply(target))));
        }
    }

    /**
     * 把制定集合中的元素转成字符串
     * @param target
     * @param delimiter 分隔符
     * @return
     */
    public static String joining(Collection< ? extends CharSequence> target,CharSequence delimiter) {
        if(isNotEmpty(target)){
            return target.stream().filter(obj -> null != obj).collect(Collectors.joining(delimiter));
        }
        return "";
    }

    public static <T> List<T> emptyList() {
        return Collections.emptyList();
    }
}


